/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:               Dstrtime.c
 *  Content:    This source file contains a Multimedia Timer Callback
 *             procedure which is used by the DirectSound Stream Sample
 *             application to update the playback buffer at regular intervals
 *
 ***************************************************************************/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <commctrl.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>

#include "dsstream.h"

extern HWND  hWndMain; 
extern char  szDebug[];
extern WAVEINFOCA wiWave;

LONG lInTimer = FALSE;

void CALLBACK TimeFunc( UINT uTimerID, UINT uMsg, DWORD dwUser,
                                                        DWORD dw1, DWORD dw2 )
    {
    LPBYTE      lpWrite1, lpWrite2, lpTemp;
    DWORD       dwLen1, dwLen2, dwPlay, dwWrite, dwPlayedLength, dwWriteLength;
    DWORD       dwLeftToRead, dwStatus;
    int         nChkErr;
    UINT        uChkErr;
    BOOL        fRefillLostBuffer = FALSE;
    HRESULT     hr;

    if (InterlockedExchange(&lInTimer, TRUE)) return;

    /* See if the buffer has been lost */
    IDirectSoundBuffer_GetStatus( wiWave.lpDSBStreamBuffer, &dwStatus );
    if( DSBSTATUS_BUFFERLOST & dwStatus )
        {
        // Restore the buffer and set some variables that will cause it
        // to be filled again and replayed
        hr = IDirectSoundBuffer_Restore( wiWave.lpDSBStreamBuffer );
        if( FAILED( hr ))
            {
            InterlockedExchange( &lInTimer, FALSE );
            return;
            }
        wiWave.dwNextWriteOffset = 0;
        fRefillLostBuffer = TRUE;
        }

    /* Get and print the current position of the play cursor */
    wiWave.lpDSBStreamBuffer->lpVtbl->GetCurrentPosition( 
                    wiWave.lpDSBStreamBuffer, &dwPlay, &dwWrite );
    
    /* If the play cursor is at the same spot as the last call, there are two
     * possibilities.  The first is that we were called extremely late and
     * happened to land on an integer number of complete buffer cycles.  This
     * is not very likely.  The other is that the play cursor didn't move.
     * Since we're oversampling, this is very likely.  In this case, we should
     * bail.
     */

    if( dwPlay == wiWave.dwNextWriteOffset && !fRefillLostBuffer )
        {
#ifdef DEBUG
        PostMessage( hWndMain, WM_DSSTREAM_DEBUG,
                MAKEWPARAM( DEBUGF_SKIP, 0 ), 0 );
#endif
        InterlockedExchange(&lInTimer, FALSE);
        return;
        }

#ifdef DEBUG
    PostMessage( hWndMain, WM_DSSTREAM_DEBUG,
                MAKEWPARAM( DEBUGF_PLAYPOSITION, 0 ), dwPlay );
#endif

#ifdef DEBUG
    PostMessage( hWndMain, WM_DSSTREAM_DEBUG,
                    MAKEWPARAM( DEBUGF_WRITEPOSITION, 0 ),
                    wiWave.dwNextWriteOffset );
#endif

    /* Have we found the end of the file and passed the buffer end? */
    if( wiWave.bFoundEnd && !wiWave.dwBytesRemaining )
        {
        if( !wiWave.bDonePlaying )
            {
            wiWave.bDonePlaying = TRUE;
            PostMessage( hWndMain, WM_DSSTREAM_DONE, (WPARAM)0, (LPARAM)0 );
            }
        InterlockedExchange(&lInTimer, FALSE);
        return;
        }

    if( dwPlay < wiWave.dwNextWriteOffset )
        {
        /* Calculate how many writeable bytes there are behind the play cursor */
        dwPlayedLength = (dwPlay + wiWave.dwBufferSize - wiWave.dwNextWriteOffset);
        }
    else
        {
        /* Calculate how many writeable bytes there are behind the play cursor */
        dwPlayedLength = (dwPlay - wiWave.dwNextWriteOffset);
        }

    // If the buffer was lost, then we need to start filling data at the start of
    //   the buffer, but we can decrease startup latency by only filling a segment
    //   or two this time around.
    if( fRefillLostBuffer )
        dwWriteLength = 2 * wiWave.dwBufferSize / NUM_BUFFER_SEGMENTS;
    else
        dwWriteLength = dwPlayedLength;

    wiWave.dwProgress += dwPlayedLength;

    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, wiWave.dwProgress );


    /*
     *  If wiWave.bFoundEnd == TRUE, then we've finished reading in the file,
     * but we need to wait until the buffer's play cursor passes the point we
     * were at when we found out we were done reading.
     */
    if( wiWave.bFoundEnd && wiWave.dwBytesRemaining )
        {
        // Decrement the count of how many bytes we have to wait for before
        // we can kill the timer procedure safely
        if( dwPlayedLength > wiWave.dwBytesRemaining )
            wiWave.dwBytesRemaining = 0;
        else
            wiWave.dwBytesRemaining -= dwPlayedLength;

        if( wiWave.lpDSBStreamBuffer->lpVtbl->Lock( wiWave.lpDSBStreamBuffer,
                                                wiWave.dwNextWriteOffset,
                                                dwWriteLength,
                                                &((LPVOID)lpWrite1), &dwLen1,
                                                &((LPVOID)lpWrite2), &dwLen2,
                                                0 ) != 0 )
            {
            OutputDebugString( "TimeFunc() could not lock DirectSoundBuffer" );
            InterlockedExchange(&lInTimer, FALSE);
            return;
            }

        /* Silence out both parts of the locked buffer */
        memset( lpWrite1, wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0, dwLen1 );

        if( lpWrite2 && dwLen2 )
            memset( lpWrite2,
                    wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0,
                    dwLen2 );

        wiWave.lpDSBStreamBuffer->lpVtbl->Unlock( wiWave.lpDSBStreamBuffer,
                                                    (LPVOID)lpWrite1, dwLen1,
                                                    (LPVOID)lpWrite2, dwLen2 );
    /*
     * This code is stolen from the end of the routine -- we need to keep
     * zeroing out buffer segments while we're waiting for the play cursor to
     * catch up to the end of the WAVE data.
     */
        wiWave.dwNextWriteOffset += dwWriteLength;
        if( wiWave.dwNextWriteOffset >= wiWave.dwBufferSize )
            wiWave.dwNextWriteOffset -= wiWave.dwBufferSize;

#ifdef DEBUG
    PostMessage( hWndMain, WM_DSSTREAM_DEBUG,
                    MAKEWPARAM( DEBUGF_NEXTWRITE, 0 ),
                    wiWave.dwNextWriteOffset );
#endif
        InterlockedExchange(&lInTimer, FALSE);
        return;
        }

    /* Lock a segment of memory that is behind the play cursor */
    if( wiWave.lpDSBStreamBuffer->lpVtbl->Lock( wiWave.lpDSBStreamBuffer,
                                            wiWave.dwNextWriteOffset,
                                            dwWriteLength,
                                            &((LPVOID)lpWrite1), &dwLen1,
                                            &((LPVOID)lpWrite2), &dwLen2,
                                            0 ) != 0 )
        {
        OutputDebugString( "TimeFunc() could not lock DirectSoundBuffer" );
        InterlockedExchange(&lInTimer, FALSE);
        return;
        }
        
    if( dwLen1 && !wiWave.bDonePlaying )
        {
        nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLen1, lpWrite1,
                                &wiWave.mmck, &uChkErr );
        if( uChkErr < (UINT)dwLen1 )
            {
            if( !wiWave.bLoopFile )
                {
                /* Zero out the rest of this block */
                if( wiWave.pwfx->wBitsPerSample == 8 )
                    memset( lpWrite1+uChkErr, 128, ((UINT)dwLen1-uChkErr));
                else if( wiWave.pwfx->wBitsPerSample == 16 )
                    memset( lpWrite1+uChkErr, 0, ((UINT)dwLen1-uChkErr));

/* Enable play completion detection code at the beginning of the next call */

                wiWave.bFoundEnd = TRUE;
                if( dwPlay > wiWave.dwNextWriteOffset )
                    wiWave.dwBytesRemaining = (wiWave.dwNextWriteOffset
                                            + wiWave.dwBufferSize - dwPlay);
                else
                    wiWave.dwBytesRemaining = (wiWave.dwNextWriteOffset
                                            - dwPlay);
                }
             else
                {
                lpTemp = lpWrite1;
                dwLeftToRead = dwLen1;
                do
                    {
                    /* Continue decrementing our count and moving our temp
                     * pointer forward until we've read the file enough times
                     * to fill the buffer.  NOTE: It's probably not efficient
                     * to bother with the overhead of streaming a file that's
                     * not at least as large as the buffer... */
                    lpTemp += uChkErr;
                    dwLeftToRead -= uChkErr;
                    nChkErr = WaveStartDataRead( &wiWave.hmmio,
                                                    &wiWave.mmck,
                                                    &wiWave.mmckInRIFF );
                    nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLeftToRead,
                                            lpTemp,
                                            &wiWave.mmck, &uChkErr );
                    } while( uChkErr < dwLeftToRead );
                }
            }
        }
    /*
    * The bDonePlaying flag is set by the caller if the user stops playback
    * before the end of the WAVE file is encountered.  It tells us to cut this
    * racket out and play nothing in case it takes the caller a couple
    * interrupts to shut off the timer.
    */
    else if( dwLen1 && wiWave.bDonePlaying )
        {
        // Set the appropriate silence value
        _fmemset( lpWrite1,
                    wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0,
                    dwLen1);
        }

    if( dwLen2 && !wiWave.bDonePlaying )
        {
        nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLen2, lpWrite2,
                                &wiWave.mmck, &uChkErr );
        if( uChkErr < (UINT)dwLen2 )
            {
            if( !wiWave.bLoopFile )
                {
                /* Zero out the rest of this block */
                if( wiWave.pwfx->wBitsPerSample == 8 )
                    memset( lpWrite2+uChkErr, 128, ((UINT)dwLen2-uChkErr));
                else if( wiWave.pwfx->wBitsPerSample == 16 )
                    memset( lpWrite2+uChkErr, 0, ((UINT)dwLen2-uChkErr));
                /* Enable play completion detection code at the beginning
                 * of the next call
                 */
                wiWave.bFoundEnd = TRUE;
                if( dwPlay > wiWave.dwNextWriteOffset )
                    wiWave.dwBytesRemaining = (wiWave.dwNextWriteOffset
                                            + wiWave.dwBufferSize - dwPlay);
                else
                    wiWave.dwBytesRemaining = (wiWave.dwNextWriteOffset
                                            - dwPlay);
                }
            else
                {
                lpTemp = lpWrite2;
                dwLeftToRead = dwLen2;
                do
                    {
                    /* Continue decrementing our count and moving our temp
                     * pointer forward until we've read the file enough times
                     * to fill the buffer.  NOTE: It's probably not efficient
                     * to bother with the overhead of streaming a file that's
                     * not at least as large as the buffer... */
                    lpTemp += uChkErr;
                    dwLeftToRead -= uChkErr;
                    nChkErr = WaveStartDataRead( &wiWave.hmmio,
                                                    &wiWave.mmck,
                                                    &wiWave.mmckInRIFF );
                    nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLeftToRead,
                                            lpTemp,
                                            &wiWave.mmck, &uChkErr );
                    } while( uChkErr < dwLeftToRead );
                }
            }
        }
    else if( lpWrite2 && dwLen2 && wiWave.bDonePlaying )
        {
        // Set the appropriate silence value
        _fmemset( lpWrite2,
                    wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0,
                    dwLen2 );
        }

    wiWave.lpDSBStreamBuffer->lpVtbl->Unlock( wiWave.lpDSBStreamBuffer,
                                                (LPVOID)lpWrite1, dwLen1,
                                                (LPVOID)lpWrite2, dwLen2 );

    wiWave.dwNextWriteOffset += dwWriteLength;
    if( wiWave.dwNextWriteOffset >= wiWave.dwBufferSize )
        wiWave.dwNextWriteOffset -= wiWave.dwBufferSize;

#ifdef DEBUG
    PostMessage( hWndMain, WM_DSSTREAM_DEBUG,
                    MAKEWPARAM( DEBUGF_NEXTWRITE, 0 ),
                    wiWave.dwNextWriteOffset );
#endif
    if( fRefillLostBuffer )
        IDirectSoundBuffer_Play( wiWave.lpDSBStreamBuffer, 0, 0, DSBPLAY_LOOPING );
    InterlockedExchange(&lInTimer, FALSE);
    return;
    }
